# -----------------------------------------------------------------------------
# Copyright (c) 2011, MIPS Technologies, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright
#     notice, this list of conditions, and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright
#     notice, this list of conditions, and the following disclaimer in the
#     documentation and/or other materials provided with the distribution.
#   * Neither the name of MIPS Technologies, Inc. nor the
#     names of its contributors may be used to endorse or promote products
#     derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MIPS TECHNOLOGIES, INC. BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES, LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# -----------------------------------------------------------------------------
'''
Created on May 16, 2011

@author: rkotler
'''
# from mips_linux_gnu_gcc_config import *
from gnu_parse import GnuParser, GnuLanguageRules

import overall_options_module;
import sys
import os
import subprocess
import DriverConfig
import Utils

#"-std=c99",

#
# an an option and set it's kind
#    
def add_option_from_tuple(parser, tuple, kind):
    option = tuple[0]
    if (len(tuple)> 1):
        definition = tuple[1]
    else:
        definition = {}
    definition["kind"] = kind
    parser.add_opt(option, definition)
 
#
# add a table of options and set them to 
# all have the same kind
#
def add_options_from_table(parser, table, kind):
  for item in table:
    add_option_from_tuple(parser, item, kind)

class CompileError(Exception):
  def __init__(self):
    pass

class Phase(object):
  def __init__(self, Tool):
    self.Tool = Tool
    self.options = []

  def execute(self, Src, Dst):
    cmd = [self.Tool]
    cmd.extend(self.options)
    if Src:
      if isinstance(Src, list):
        cmd = [cmd[0]] + Src + cmd[1:]
      else:
        cmd.insert(1, Src)

    if Dst:
      cmd.extend(["-o", Dst])

    #print cmd
    #print "cmd: " + " ".join(cmd)
    #cmd.append("-v")
    if subprocess.call(cmd):
      raise CompileError()

  def prependoptions(self, o):
    self.options = o + self.options

  def extendoptions(self, o):
    self.options.extend(o)

  def appendoption(self, o):
    self.options.append(o)

class Task(object):
  def __init__(self, Phase, Src, Dst, DstTy):
    self.Phase = Phase
    self.Src = Src
    self.Dst = Dst
    self.DstTy = DstTy

  def execute(self):
    if (self.Phase):
      return self.Phase.execute(self.Src, self.Dst)

  def dstTy(self):
    return self.DstTy

class GCCGeneric(object):

  Source, PreProcessed, Bitcode, Assembly, Object, Help = range(6)

  MipsArchs = { "mips32" : "mips", "mips64" : "mips64" }
  Endians = [ "EL", "EB" ]
  Relocs = { "fPIC" : "pic", "static" : "static" }
  MipsABIs = {
    "32" : ("o32", ("-D_MIPS_SZPTR=32", "-D_ABIO32=1",  "-D_MIPS_SIM=_ABIO32")),
    "64" : ("n64", ("-D_MIPS_SZPTR=64", "-D_ABI64=3",  "-D_MIPS_SIM=_ABI64")),
    "n32" :("n32", ("-D_MIPS_SZPTR=32", "-D_ABIN32=2",  "-D_MIPS_SIM=_ABIN32")) }

  MipsLLCOptions = ("-enable-mips-delay-filler", "-mips-fix-global-base-reg")

  def isMipsLLCOption(self, o):
    for mipsopt in GCCGeneric.MipsLLCOptions:
      if o.find(mipsopt) == 0:
        return True
    return False

  def __init__(self, mode):
    self.mode = mode 
    self.cfg = DriverConfig.Config(mode)
    self.cfg.read()

    if mode == DriverConfig.Config.MODE_GXX:
      self.clang = Phase(self.cfg.get_clangxx_loc())
    else:
      self.clang = Phase(self.cfg.get_clang_loc()) #front end. 
    #self.clangxx = Phase(self.cfg.get_clangxx_loc())
    self.c_compiler_cg = Phase(self.cfg.get_llvm_loc())  #code generator for c language
    self.assembler = Phase(self.cfg.get_asm_loc())  # assmebler
    self.assembler.DstTy = GCCGeneric.Object
    self.assembler.appendoption("-c")

    self.linker = Phase(self.cfg.get_ld_loc())  # linker

    self.o_files = []  #these need to be collected

    # can stop after various phases
    self.stop_after_cpp = False
    self.stop_after_compile = False
    self.stop_after_as = False
    self.do_print = False #-print option present
    self.o_opt = []  # -o

    self.print_multi_lib = ""
    self.print_multi_directory = ""

    # a list of tuples
    #
    self.source_files = []  #source files
    self.all_options = []   #options

    self.doing_help = False

    self.gparser = GnuParser()
    self.glrules = GnuLanguageRules()

    add_options_from_table(self.gparser, overall_options_module.gnu_options, "gnu")
    add_options_from_table(self.gparser, overall_options_module.gcc_c_options, 'c')
    add_options_from_table(self.gparser, overall_options_module.gcc_common_options, 'common') 
    add_options_from_table(self.gparser, overall_options_module.preprocessor_options, "prep")
    add_options_from_table(self.gparser, overall_options_module.directory_options, "dir")
    add_options_from_table(self.gparser, overall_options_module.linking_options, "linker")
    add_options_from_table(self.gparser, overall_options_module.mips_options, "misc")
    add_options_from_table(self.gparser, overall_options_module.llvm_options, "misc")

    self.arch = "mips32"
    self.cpu = "mips32"
    self.endian = "EL"
    self.abi = "32"
    self.relocmodel = "fPIC"

    self.DstTy = None
    self.DoLink = False
    self.emitObj = False
    self.ObjectFiles = []

  def buildall(self):
    try:
      self.parseOptions()
    
      if self.doing_help:
        self.printHelp(overall_options_module.gnu_options)
        return

      self.processOptions()
      self.processFiles()
  
      if self.DoLink:
        self.doLink()
        
      return 0

    except CompileError:
      return 1

  def printHelp(self, table):
    for item in table:
      print "   " + item[0],
      if len(item) > 1:
        if "help" in item[1]:
          print item[1]["help"]

  def parseOptions(self):
    for lang in overall_options_module.languages:
      self.glrules.add_language(lang);

    for langrule in overall_options_module.default_language_suffix :
      lang = langrule[0]
      if (type(langrule[1]) ==str):
        self.glrules.add_suffix(langrule[1], lang)
      else:
        for suffix in langrule[1]:
          self.glrules.add_suffix(suffix, lang)    
    
    for i in self.gparser.long_opt:
      pass

    rest = sys.argv[1:]

    if not rest:
      return

    result = self.gparser.do_parse(rest)

    # options
    for o in result[0]:
      self.all_options.append(o)
    
    # files
    for filename in result[1]: 
      basename, extension = os.path.splitext(filename)
      plain_extension = extension[1:]
      language = self.glrules.find_language(plain_extension)
      todo = [filename, language]
      self.source_files.append(todo)

  def processOptions(self):
    for opt0 in self.all_options:
      if type(opt0)==str:
        optname = opt0
        opt = [optname]
      else:
        optname = opt0[0]
        opt = [opt0[0] + opt0[1]]

      #if opt in overall_options_module:
      if self.process_target_options(opt0):
        continue
      if self.process_llvm_options(opt0):
        continue
      if self.process_special_options(optname, opt):
        continue

      kind = self.gparser.get_opt_kind(optname)

      if (kind == "prep"):
        self.clang.extendoptions(opt)
      elif (kind == "dir"):
        self.clang.extendoptions(opt)
      elif (kind == "linker"):
        self.linker.extendoptions(opt)
      elif (kind == "common"):
        self.clang.extendoptions(opt)
      elif (kind == "gnu"):
        self.clang.extendoptions(opt)
      elif (kind == "c"):
        self.clang.extendoptions(opt)

    self.analyzeOptionsFinal()

  def process_target_options(self, opt):
    if isinstance(opt, list):
      if opt[0] == "-mabi=":
        assert GCCGeneric.MipsABIs.has_key(opt[1])
        self.abi = opt[1]
        return True
      elif opt[0] == "-mips-fix-global-base-reg=" or \
           opt[0] == "-enable-mips-delay-filler=":
        self.c_compiler_cg.appendoption(opt[0] + opt[1])
        return True
      return False
    else:
      if GCCGeneric.MipsArchs.has_key(opt[1:].replace("r2", "")):
        self.arch = opt[1:].replace("r2", "")
        self.cpu = opt[1:]
      elif opt[1:] in GCCGeneric.Endians:
        self.endian = opt[1:]
      elif self.isMipsLLCOption(opt):
        self.c_compiler_cg.appendoption(opt)
      elif GCCGeneric.Relocs.has_key(opt[1:]):
        self.relocmodel = opt[1:]
      else:
        return False

    return True

  def process_llvm_options(self, opt):
    if opt == "-emit-obj":
      self.emitObj = True
    elif opt == "-direct":
      self.direct = True
    elif opt == "-emit-llvm":
      self.DstTy = GCCGeneric.Bitcode
    else:
      return False

    return True

  def process_special_options(self, optname, opt):
    if optname == "-c":
      self.DstTy = GCCGeneric.Object
    elif optname == "-S":
      self.DstTy = GCCGeneric.Assembly
    elif optname == "-E":
      self.DstTy = GCCGeneric.PreProcessed
    elif optname == "-o":
      self.o_opt = opt
    elif optname == "--version":
      self.DstTy = GCCGeneric.Help
    else:
      return False

    return True

  def analyzeOptionsFinal(self):
    if self.DstTy == None:
      self.DstTy = GCCGeneric.Object
      self.DoLink = True
    else:
      self.DoLink = False

    self.Phases = {}

    if self.DstTy == GCCGeneric.PreProcessed:
      self.clang.DstTy = GCCGeneric.PreProcessed
      self.clang.appendoption("-E")
      self.Phases[GCCGeneric.Source] = [self.clang]
    elif self.DstTy == GCCGeneric.Help:
      self.clang.DstTy = GCCGeneric.Help
      self.clang.appendoption("--version")
      self.Phases[GCCGeneric.Source] = [self.clang]
    elif self.DstTy == GCCGeneric.Assembly:
      self.clang.DstTy = GCCGeneric.Bitcode
      self.c_compiler_cg.DstTy = GCCGeneric.Assembly
      self.Phases[GCCGeneric.Source] = self.Phases[GCCGeneric.PreProcessed] = \
          [self.clang, self.c_compiler_cg]
      self.Phases[GCCGeneric.Bitcode] = [self.c_compiler_cg]
    elif self.DstTy == GCCGeneric.Bitcode:
      self.clang.DstTy = GCCGeneric.Bitcode
      self.Phases[GCCGeneric.Source] = self.Phases[GCCGeneric.PreProcessed] = \
          [self.clang]
      self.clang.appendoption("-emit-llvm")
      self.clang.appendoption("-S")
    elif self.DstTy == GCCGeneric.Object:
      self.clang.DstTy = GCCGeneric.Bitcode

      self.Phases[GCCGeneric.Source] = self.Phases[GCCGeneric.PreProcessed] = \
        [self.clang, self.c_compiler_cg]
      self.Phases[GCCGeneric.Bitcode] = [self.c_compiler_cg]
      self.Phases[GCCGeneric.Assembly] = [self.assembler]

      if self.emitObj:
        self.c_compiler_cg.DstTy = GCCGeneric.Object
        self.c_compiler_cg.appendoption("-filetype=obj")
      else:
        self.c_compiler_cg.DstTy = GCCGeneric.Assembly
        self.Phases[GCCGeneric.Source].append(self.assembler)
        self.Phases[GCCGeneric.Bitcode].append(self.assembler)

    else:
      assert False

    # mips options
    self.assembler.prependoptions(["-" + self.cpu, "-" + self.endian,
                                    "-mabi=" + self.abi, "-" + self.relocmodel])
    self.linker.prependoptions(["-" + self.cpu, "-" + self.endian,
                                    "-mabi=" + self.abi, "-" + self.relocmodel])
    endian = self.endian.lower()
    abi, macros = GCCGeneric.MipsABIs[self.abi]
    relocmodel = GCCGeneric.Relocs[self.relocmodel]

    tgtstr = GCCGeneric.MipsArchs[self.arch] + endian
    clang_opts = ["-target", tgtstr + "-unknown-linux",  
                  "-emit-llvm", "-S", "-mabi=" + abi]
     #             "-emit-llvm", "-O3", "-S", "-mabi=" + abi]
    clang_opts.extend(macros)

    self.clang.extendoptions(clang_opts)
    #self.clangxx.extendoptions(clang_opts)

    self.c_compiler_cg.extendoptions(["-mcpu=" + self.cpu, "-mattr=" + abi,
                                      "-relocation-model=" + relocmodel])

    targetStr = "-".join([self.arch, self.endian, self.abi])
    
    if self.mode == DriverConfig.Config.MODE_GXX:
      c_include_opts = Utils.create_arg_list('-isystem',
                                             self.cfg.get_includes(True, targetStr))
    else:
      c_include_opts = Utils.create_arg_list('-isystem',
                                             self.cfg.get_includes(False, targetStr))
    self.clang.extendoptions(c_include_opts)
    #self.clangxx.extendoptions(cxx_include_opts)

  def getFile(self, filename, Ty):
    simplefilename = os.path.split(filename)[1]
    fileroot = os.path.splitext(simplefilename)[0]
    return fileroot + "." + overall_options_module.FileSuffix[Ty][0]

  def fileType(self, filename):
    ext = os.path.splitext(filename)[1]
    
    for i, suffls in enumerate(overall_options_module.FileSuffix):
      if ext[1:] in suffls:
        return i
    
    assert False, "suffix not found: " + filename

  def Preprocess(self):
    Src = [f[0] for f in self.source_files if self.fileType(f[0]) == GCCGeneric.Source]

    Dst = None
    if self.o_opt:
      Dst = self.o_opt[0][2:]

    self.Phases[GCCGeneric.Source][0].execute(Src, Dst)

  def PrintVersion(self):
    self.clang.execute(None, None)

  def getTasks(self, Src):
    SrcTy = self.fileType(Src)

    if SrcTy == self.DstTy:
      return [Task(None, Src, Src, SrcTy)]

    if SrcTy > self.DstTy:
      return []

    tasks = []

    for phase in self.Phases[SrcTy]:
      Dst = self.getFile(Src, phase.DstTy)
      tasks.append(Task(phase, Src, Dst, phase.DstTy))
      Src = Dst 

    if not self.DoLink and self.o_opt:
      tasks[len(tasks) - 1].Dst = self.o_opt[0][2:]

    return tasks

  def processFiles(self):
    if self.DstTy == GCCGeneric.PreProcessed:
      self.Preprocess()
      return

    if self.DstTy == GCCGeneric.Help:
      self.PrintVersion()
      return

    for i in self.source_files:
      lang = i[1]
      filename = i[0]

      # if object file, add to list of o files
      # get list of tasks
      # if none, continue
      # execture tasks

      # get the list of tasks and execute.
      tasks = self.getTasks(filename)

      if not tasks:
        continue

      for t in tasks:
        t.execute()

      # add object file to list.
      if tasks[len(tasks) - 1].dstTy() == GCCGeneric.Object:
        self.ObjectFiles.append(tasks[len(tasks) - 1].Dst)

  def doLink(self):
    if self.o_opt:
      Dst = self.o_opt[0][2:]
    else:
      Dst = "a.out"

    self.linker.execute(self.ObjectFiles, Dst)
